#
# Copyright 2009, Jeffrey Palm.
#

require 'socket'

SCRIPT_FILE  = 'itunes.scpt'

#
# For the people...
#

# Prints a message to STDERR
def note(s)
  STDERR.puts '*** ' + s
end

# Prints out an error message to STDERR and returns false so I can
# just say 'return error e' in the exception handler guarding setting
# the shared key
def error(s)
  STDERR.puts 'Error: ' + s
  false
end

# Returns the URL based on the base url
def url(config,page)
  config.base_url + page
end

# Opens and fully-reads the url
def open_and_read(url)
  note 'Openning url: ' + url
  open(url).read
end

# Returns the last line of the response
def last_line(content)
  lines = content.split /\n/
  lines[lines.length-1]
end

# Returns the public IP address of this machine
def public_ip_address
  /([\d\.]+)/.match(open('http://checkip.dyndns.org').read)[1]
end

# Returns the private IP address of this machine
#
# From here:
#   http://coderrr.wordpress.com/2008/05/28/get-your-local-ip-address/
#
def private_ip_address
  orig, Socket.do_not_reverse_lookup = Socket.do_not_reverse_lookup, true  
  # turn off reverse DNS resolution temporarily
  
  UDPSocket.open do |s|
    s.connect '64.233.187.99', 1
    s.addr.last
  end
ensure
  Socket.do_not_reverse_lookup = orig
end

# Removes all quotes and any harmful or distracting from the string,
# 's'.  Basically, we want to be able to match string that were
# bought in the itunes store, amazon store, Limewire store (hoowoo),
# or possibly a CD and then converted.  In the authors experience,
# the meta-information is sometimes normalized a bit, so it's better
# to consiere strings without quotes, commas, periods, and other
# non-a-to-z stuff.  Right now, negative regular expression aren't
# working too well in Ruby, so we'll have to stick with simply
# removing the normal bad stuff.  Ideally, we want every but letters
# and numbers.
def remove_nasty_stuff(s)
  real_s = s.to_s
  if s
    s = URI.decode s
    s = s.strip            # itunes sucks at whitespace
    s = s.gsub /\'/,    ''
    s = s.gsub /\"/,    ''
    s = s.gsub /\s*\.\s*/,   ''
    s = s.gsub /\s*\,\s*/,   ''
    s = s.gsub /-/,   ' ' # not exactly sure why?
    s = s.gsub /\s+/, ' ' # we'll be nice
    s = s.downcase
  end
  return s
end

# Compares the strings with too much scrutiny
def compare_leniently(a,b)

  return true if a == nil              # This is the wanted value,

  # it can be nil
  return true if a == nil && b == nil  # If we don't have a wanted

  # or gotten value, these areequal, too
  return remove_nasty_stuff(a) == remove_nasty_stuff(b)    
end

# Returns a file to which we pipe results.  This won't *always* return
# the same thing twice.
def result_file
  'result.txt' # TODO
end


# Runs a script 's' with optional arguments 'arg'
def run_script(s,arr=[])
  file = write_script_file s
  f = result_file
  system 'osascript ' + file + ' ' + arr.join(' ').to_s + ' > ' + f
  line = IO.readlines(f)[0]
  res = line ? line.split(/\t/) : []

  # Clean up
  delete_file f
  return res
end

# Deletes the file 'f'
def delete_file(f)
  note 'Deleting ' + f
  File.delete f if File.exist? f
end

# Write the string 's' to the script file
def write_script_file(s)
  File.open SCRIPT_FILE,'w' do |out|
    out.write s
  end
  return SCRIPT_FILE
end

# Given the song 'info' returns nicely formatted rows of the info's
# key/value pairs
def info_to_string(info)
  res = nil
  space = max_string_length info.keys
  info.each do |k,v|
    res  = res ? res+"\n" : ''
    res += '' * space
    res += ' '
    res += k.to_s + ' : ' + v.to_s
  end
  return res
end

# Returns the max length of the strings in arr, or 0, if they are
# all nil of empty
def max_string_length(arr)
  max_string = ''
  arr.each do |s|
    max_string = s if not s || s.length > max_string.length
  end
  return max_string.length
end

# Take an itunes-like music library and find a track given info
def find_track_from_xml(xml)

  note 'Find track info:'
  note info_to_string info
  
  # Right now we only care about macs and iphones
  if not File.exist? xml
    note 'Couldn\'t find itunes library at: ' + xml
    return nil
  end
  
  lines = open(xml).read.split /\n/
  
  # This is the actual track we're looking for, but, as you'll see,
  # we're cut some corners or matching an exact track
  note 'Looking for:'
  note ' artist : ' + info['artist'].to_s
  note ' album  : ' + info['album'].to_s
  note ' track  : ' + info['track'].to_s
  
  track_id = nil
  name     = nil
  artist   = nil
  album    = nil
  
  # Because it's easier we'll remove all quote from the song and
  # from the strings in the xml.  Create a new, imutable hash,
  # containing none of this nasty stuff.
  new_info = {}
  info.each do |k,v|
    next if not k # shouldn't happen
    next if not v # ok if it happens
    new_info[k] = remove_nasty_stuff v
  end
  info = new_info
  
  lines.each do |line|
    
    # <key>Track ID</key><integer>1141</integer>
    if not track_id
      line.scan /<key>Track ID<\/key><integer>(\d+)<\/integer>/ do |res|
        track_id = res[0]
      end
    end
    
    # <key>Artist</key><string>Sage Francis</string>
    if not artist
      line.scan /<key>Artist<\/key><string>([^<]+)<\/string>/ do |res|
        artist = res[0]
      end
    end
    # <key>Name</key><string>Hoofprints In the Sand</string>
    if not name
      line.scan /<key>Name<\/key><string>([^<]+)<\/string>/ do |res|
        name = res[0]
      end
    end

    # <key>Album</key><string>Human the Death Dance</string>
    if not album
      line.scan /<key>Album<\/key><string>([^<]+)<\/string>/ do |res|
        album = res[0]
      end
    end

    if track_id and name and artist and album

      debug 'Found a some track' if @verbose
      
      # Do an easy compare, that is...  
      #  - if we find an artist on this device, but none on the
      #    other, use that artist
      #  - if we find an artist and album on this device, but an
      #    artist not but album on the other, use that artist
      #  - if we find an artist and album and track on the other
      #    device, but only an artist and album on this, use this
      #    track
      if ( compare_leniently(info['artist']  , artist) &&
           compare_leniently(info['album']   , album ) &&
           compare_leniently(info['track']    , name )   )
        return Track.new name,artist,album,track_id
      end
      track_id  = nil
      name      = nil
      artist    = nil
      album     = nil
    end
  end
  return nil
end

# Throws an exception because this method isn't implemented
def eek(method_name)
  throw method_name + ' must be implemented'
end
